﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Text;
using nwrole.plugin;

namespace nwrole.lib {
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class OrchestratorService : IOrchestratorContract, IOrchestratorWebsiteContract {
        private List<IOrchestratorClientContract> supervisors = new List<IOrchestratorClientContract>();

        public void Stop() {
            foreach(var supervisor in supervisors.ToArray()) {
                ICommunicationObject obj = (ICommunicationObject)supervisor;
                obj.Abort(); // Call Abort() and not Close() otherwise the client won't get notified
            }
        }

        public int NumConnectedSupervisors {
            get { return supervisors.Count; }
        }

        /*public string GetSupervisorAddress(int index) {
            return ((ICommunicationObject)supervisors[index]).
        }*/

        public SupervisorInfo[] GetRegisteredSupervisors()
        {
            try
            {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
                {
                    List<SupervisorInfo> supervisorInfos = new List<SupervisorInfo>();

                    SupervisorData[] datas = GetSupervisors();

                    foreach(var dbSupervisor in context.DbSupervisors)
                    {
                        SupervisorInfo info = new SupervisorInfo();
                        info.Id = dbSupervisor.Id;
                        info.DisplayName = dbSupervisor.DisplayName;
                        SupervisorData data = datas.SingleOrDefault(d => d.Id == info.Id);
                        if(data != null)
                            info.Status = SupervisorStatus.Connected;
                        else
                            info.Status = SupervisorStatus.Disconnected;

                        info.LastRegistered = dbSupervisor.LastRegistered;
                        info.LastSeen = dbSupervisor.LastSeen;

                        supervisorInfos.Add(info);
                    }

                    return supervisorInfos.ToArray();
                }
            }
            catch (Exception ex)
            {
                //OrchestratorLog(LogLevel.Error, "Unable to insert/update DB with Supervisor id: " + supervisorId + " displayName: " + displayName, ex);
            }

            return null;
        }

        public SupervisorData[] GetSupervisors() {
            SupervisorData[] datas = new SupervisorData[supervisors.Count];
            for (int i = 0; i < supervisors.Count; i++ ) {
                datas[i] = supervisors[i].GetSupervisorData();
            }

            return datas;
        }

        /*public Result StartSupervisor(Guid supervisorId)
        {
            IOrchestratorClientContract supervisor = supervisors.Single(s => s.GetSupervisorData().Id == supervisorId);
            return supervisor.Start();
        }

        public Result StopSupervisor(Guid supervisorId)
        {
            IOrchestratorClientContract supervisor = supervisors.Single(s => s.GetSupervisorData().Id == supervisorId);
            return supervisor.Stop();
        }*/

        public Result RestartSupervisor(Guid supervisorId)
        {
            IOrchestratorClientContract supervisor = supervisors.Single(s => s.GetSupervisorData().Id == supervisorId);
            supervisor.Stop();
            return supervisor.Start();
        }

        public Result RestartService(Guid serviceId)
        {
            try
            {
                IOrchestratorClientContract supervisor = GetSupervisorFromService(serviceId);

                return supervisor.RestartService(serviceId);
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "Unable to restart service with id: " + serviceId, ex);
            }

            return Result.UnknownError;
        }

        public Result StopService(Guid serviceId)
        {
            try
            {
                IOrchestratorClientContract supervisor = GetSupervisorFromService(serviceId);

                return supervisor.StopService(serviceId);
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "Unable to stop service with id: " + serviceId, ex);
            }

            return Result.UnknownError;
        }

        public Result StartService(Guid serviceId)
        {
            try
            {
                IOrchestratorClientContract supervisor = GetSupervisorFromService(serviceId);

                return supervisor.StartService(serviceId);
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "Unable to start service with id: " + serviceId, ex);
            }

            return Result.UnknownError;
        }

        private IOrchestratorClientContract GetSupervisorFromService(Guid serviceId)
        {
            using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
            {
                DbService dbService = context.DbServices.SingleOrDefault(s => s.Id == serviceId);
                if (dbService == null)
                    return null;

                IOrchestratorClientContract supervisor = supervisors.Single(s => s.GetSupervisorData().Id == dbService.SupervisorId);
                return supervisor;
            }
        }

        public ServiceInfo[] GetSupervisorServicesInfo(Guid supervisorId) {
            List<ServiceInfo> infos = new List<ServiceInfo>();

            using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
            {
                DbSupervisor dbSupervisor = context.DbSupervisors.SingleOrDefault(s => s.Id == supervisorId);
                if (dbSupervisor == null)
                    return null;

                IOrchestratorClientContract supervisor = supervisors.SingleOrDefault(s => s.GetSupervisorData().Id == supervisorId);
                ServiceData[] datas;
                if(supervisor != null)
                    datas = supervisor.GetServices();
                else
                    datas = new ServiceData[0];

                foreach(var dbService in context.DbServices.Where(s => s.SupervisorId == supervisorId))
                {
                    ServiceInfo info = new ServiceInfo();
                    info.Id = dbService.Id;
                    info.LastRegistered = dbService.LastRegistered;
                    info.SupervisorId = dbService.SupervisorId;
                    info.Name = dbService.Name;

                    ServiceData serviceData = datas.SingleOrDefault(d => d.Id == dbService.Id);
                    if(serviceData != null)
                    {
                        info.State = serviceData.ServiceState;
                    }
                    else
                    {
                        info.State = ServiceState.NotFound;
                    }

                    infos.Add(info);
                }
                
                /*IOrchestratorClientContract supervisor = supervisors.Single(s => s.GetSupervisorData().Id == supervisorId);
                ServiceData[] datas = supervisor.GetServices();

                foreach (ServiceData data in datas)
                {
                    ServiceInfo info = new ServiceInfo();
                    info.Id = data.Id;
                    info.Name = data.Name;
                    info.State = data.ServiceState;
                    
                    DbService dbService = context.DbServices.SingleOrDefault(s => s.Id == data.Id);
                    if (dbService != null)
                    {
                        info.LastRegistered = dbService.LastRegistered;
                        info.SupervisorId = dbService.SupervisorId;
                    }

                    infos.Add(info);
                }*/
            }

            return infos.ToArray();
        }

        public ServiceInfo GetServiceInfo(Guid serviceId)
        {
            try
            {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
                {
                    DbService dbService = context.DbServices.SingleOrDefault(s => s.Id == serviceId);
                    if (dbService == null)
                        return null;

                    ServiceInfo[] serviceInfos = GetSupervisorServicesInfo(dbService.SupervisorId);
                    if (serviceInfos == null)
                        return null;

                    ServiceInfo serviceInfo = serviceInfos.SingleOrDefault(s => s.Id == serviceId);
                    if (serviceInfo == null)
                        return null;

                    return serviceInfo;
                }
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "Unable to get service with id: " + serviceId, ex);
            }

            return null;
        }

        public PluginInfo[] GetServicePluginsInfo(Guid serviceId)
        {
            try
            {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
                {
                    DbService dbService = context.DbServices.SingleOrDefault(s => s.Id == serviceId);
                    if (dbService == null)
                        return null;

                    IOrchestratorClientContract supervisor = supervisors.SingleOrDefault(s => s.GetSupervisorData().Id == dbService.SupervisorId);
                    
                    //PluginData[] pluginDatas = supervisor.GetServicePluginsData(serviceId);

                    List<PluginInfo> pluginInfos = new List<PluginInfo>();

                    foreach (var dbPlugin in context.DbPlugins.Where(p => p.ServiceId == serviceId))
                    {
                        PluginInfo pluginInfo = new PluginInfo();
                        pluginInfo.Id = dbPlugin.Id;
                        pluginInfo.DisplayName = dbPlugin.DisplayName;
                        pluginInfo.LastRegistered = dbPlugin.LastRegistered;

                        PluginState? pluginState = null;

                        if(supervisor != null)
                            pluginState = supervisor.GetServicePluginState(serviceId, dbPlugin.Id);

                        if(!pluginState.HasValue)
                            pluginState = PluginState.NotFound;
                        
                        pluginInfo.State = pluginState.Value;

                        pluginInfos.Add(pluginInfo);
                    }

                    /*foreach(var pluginData in pluginDatas)
                    {
                        PluginInfo pluginInfo = new PluginInfo();
                        pluginInfo.Id = pluginData.Id;
                        pluginInfo.DisplayName = pluginData.DisplayName;

                        var dbPlugin = context.DbPlugins.SingleOrDefault(s => s.Id == pluginData.Id);
                        if (dbPlugin != null)
                        {
                            pluginInfo.LastRegistered = dbPlugin.LastRegistered;
                            PluginState? pluginState = supervisor.GetServicePluginState(serviceId, pluginData.Id);
                            if (pluginState.HasValue)
                                pluginInfo.State = pluginState.Value;
                        }

                        pluginInfos.Add(pluginInfo);
                    }*/

                    return pluginInfos.ToArray();
                }
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "OrchestratorService.GetServicePluginsInfo() unable to get plugins for service with id: " + serviceId, ex);
            }

            return null;
        }

        public void RegisterSupervisor(Guid supervisorId, string displayName) {
            OrchestratorLog(LogLevel.Info, "RegisterSupervisor id:" + supervisorId + " name:" + displayName, null);
            
            IOrchestratorClientContract callback = OperationContext.Current.GetCallbackChannel<IOrchestratorClientContract>();
            
            ICommunicationObject obj = (ICommunicationObject)callback;
            obj.Closed += ClientClosed;
            obj.Closing += ClientClosing;
            obj.Faulted += ClientFaulted;

            supervisors.Add(callback);

            try {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString)) {
                    DbSupervisor dbSupervisor = context.DbSupervisors.SingleOrDefault(s => s.Id == supervisorId);
                    if (dbSupervisor == null) {
                        dbSupervisor = new DbSupervisor() {Id = supervisorId};
                        context.DbSupervisors.InsertOnSubmit(dbSupervisor);
                    }

                    dbSupervisor.DisplayName = displayName;
                    dbSupervisor.LastRegistered = DateTime.UtcNow;
                    dbSupervisor.LastSeen = dbSupervisor.LastRegistered;
                    context.SubmitChanges();
                }
            } catch(Exception ex) {
                OrchestratorLog(LogLevel.Error, "Unable to insert/update DB with Supervisor id: " + supervisorId + " displayName: " + displayName, ex);
            }
        }

        public void RegisterService(Guid supervisorId, Guid serviceId, string name) {

            OrchestratorLog(LogLevel.Info, "RegisterService supervisorId:" + supervisorId + " serviceId:" + serviceId + " name:" + name, null);

            try {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString)) {
                    DbService dbService = context.DbServices.SingleOrDefault(s => s.Id == serviceId);
                    if (dbService == null) {
                        dbService = new DbService() { Id = serviceId };
                        context.DbServices.InsertOnSubmit(dbService);
                    }

                    dbService.Name = name;
                    dbService.SupervisorId = supervisorId;
                    dbService.LastRegistered = DateTime.UtcNow;
                    context.SubmitChanges();
                }
            } catch (Exception ex) {
                OrchestratorLog(LogLevel.Error, "Unable to insert/update DB with Service id: " + serviceId + " displayName: " + name + " supervisorId: " + supervisorId, ex);
            }
        }

        public void RegisterPlugin(Guid serviceId, Guid pluginId, string displayName) {
            OrchestratorLog(LogLevel.Info, "RegisterPlugin serviceId:" + serviceId + " pluginId:" + pluginId + " name:" + displayName, null);

            try {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString)) {
                    DbPlugin dbPlugin = context.DbPlugins.SingleOrDefault(p => p.Id == pluginId);
                    if (dbPlugin == null) {
                        dbPlugin = new DbPlugin() { Id = pluginId };
                        context.DbPlugins.InsertOnSubmit(dbPlugin);
                    }

                    dbPlugin.DisplayName = displayName;
                    dbPlugin.ServiceId = serviceId;
                    dbPlugin.LastRegistered = DateTime.UtcNow;
                    context.SubmitChanges();
                }
            } catch (Exception ex) {
                OrchestratorLog(LogLevel.Error, "Unable to insert/update DB with Plugin id: " + pluginId + " displayName: " + displayName + " serviceId: " + serviceId, ex);
            }
        }

        public LogMessage[] GetLogForObject(Guid objectId)
        {
            try
            {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString))
                {
                    return context.LogMessages.Where(m => m.ObjectId == objectId).OrderByDescending(m => m.Datetime).ToArray();
                }
            }
            catch (Exception ex)
            {
                OrchestratorLog(LogLevel.Error, "Unable to retreive log for object with id: " + objectId, ex);
            }

            return null;
        }

        void ClientClosing(object sender, EventArgs e) 
        {
            OrchestratorLog(LogLevel.Info, "Supervisor closing", null);
        }

        void ClientClosed(object sender, EventArgs e) {
            OrchestratorLog(LogLevel.Info, "Supervisor closed", null);
            
            IOrchestratorClientContract callback = ((IOrchestratorClientContract)sender);
            if( supervisors.Contains(callback))
                supervisors.Remove(callback);
            else
                OrchestratorLog(LogLevel.Warning, "Supervisor client not found for removal", null);
        }

        void ClientFaulted(object sender, EventArgs e) {
            OrchestratorLog(LogLevel.Info, "Supervisor faulted", null);

            IOrchestratorClientContract callback = ((IOrchestratorClientContract)sender);
            if (supervisors.Contains(callback))
                supervisors.Remove(callback);
            else
                OrchestratorLog(LogLevel.Warning, "Supervisor client not found for removal", null);
        }

        /*public void LogSupervisor(Guid supervisorId, LogLevel level, string msg, Exception ex) {
            WriteLog("OrchestratorService.LogSupervisor: " + msg + ": " + ex);
        }

        public void LogService(Guid serviceId, LogLevel level, string msg, Exception ex) {
            WriteLog("OrchestratorService.LogService: " + msg + ": " + ex);
        }

        public void LogPlugin(Guid pluginId, LogLevel level, string msg, Exception ex) {
            WriteLog("OrchestratorService.LogPlugin: " + msg + ": " + ex);
        }*/

        public void OrchestratorLog(LogLevel level, string message, Exception ex) {
            Log(NwroleObjectType.Orchestrator, null, level, message, ex);
        }

        public void Log(NwroleObjectType? objectType, Guid? objectId, LogLevel level, string message, Exception ex) {
            try {
                WriteLog(level.ToString() + " type:" + (objectType.HasValue ? objectType.Value.ToString() : "null") + " id:" + (objectId.HasValue ? objectId.Value.ToString() : "null") + " msg:" + message + " ex:" + ex);
            }
            catch { }

            try {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString)) {
                    LogMessage msg = new LogMessage() { ObjectType = (int?)objectType, ObjectId = objectId, Message = message, Level = (int)level };
                    msg.Exception = ex != null ? ex.ToString() : null;
                    msg.Datetime = DateTime.UtcNow;

                    context.LogMessages.InsertOnSubmit(msg);
                    
                    context.SubmitChanges();
                }
            } catch (Exception e) {
                WriteLog("Unable to Log to DB exception: " + e);
            }
        }

        private static void WriteLog(string content) {
            try {
                string logDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "log/Orchestrator/");
                if (!System.IO.Directory.Exists(logDir))
                    System.IO.Directory.CreateDirectory(logDir);

                DateTime now = DateTime.Now;

                System.IO.File.AppendAllText(logDir + "log " + now.ToString("yyyy-MM-dd") + ".txt", now.ToString("yyyy-MM-dd HH:mm:ss ") + content + "\r\n");
            } catch {
            }
        }

        public void RegisterWebsite(Guid websiteId, string displayName) {
            OrchestratorLog(LogLevel.Info, "RegisterWebsite id:" + websiteId + " name:" + displayName, null);

            try {
                using (OrchestratorModelDataContext context = new OrchestratorModelDataContext(GlobalSettings.OrchestratorModelConnectionString)) {
                    DbWebsite dbWebsite = context.DbWebsites.SingleOrDefault(p => p.Id == websiteId);
                    if (dbWebsite == null) {
                        dbWebsite = new DbWebsite() { Id = websiteId };
                        context.DbWebsites.InsertOnSubmit(dbWebsite);
                    }

                    dbWebsite.DisplayName = displayName;
                    context.SubmitChanges();
                }
            } catch (Exception ex) {
                OrchestratorLog(LogLevel.Error, "Unable to insert/update DB with Website id: " + websiteId + " displayName: " + displayName, ex);
            }
        }
    }
}
